use super::{
    buf::Buf, cfg::Cfg, err, err_value, json, Base, Client, ECode, MapSv, RNull, TBase, VBase,
    VMap, Value,
};
use std::collections::HashSet;

pub struct SelBox {
    line: String,
    cfg: Cfg,
}

impl Base for SelBox {}
impl TBase for SelBox {}

impl SelBox {
    pub fn new(line: &str, sel_sn: &str) -> Self {
        Self {
            line: line.to_string(),
            cfg: Cfg::new(line, sel_sn),
        }
    }

    // get all outer box's sn and pos
    pub async fn outer(&self, client: &Client) -> Result<MapSv, Value> {
        let cfg = self.cfg.get(client).await?;

        let boxv = self.cfg.outer_box(&cfg)?;

        self.cont(&boxv, client).await
    }

    // get all inner box's sn and pos
    pub async fn inner(&self, client: &Client) -> Result<MapSv, Value> {
        let cfg = self.cfg.get(client).await?;

        let boxv = self.cfg.inner_box(&cfg)?;

        self.cont(&boxv, client).await
    }

    // find sn in outer box
    pub async fn outer_find(&self, sn: &str, client: &Client) -> Result<MapSv, Value> {
        let cfg = self.cfg.get(client).await?;

        let boxv = self.cfg.outer_box(&cfg)?;

        self.find(&boxv, sn, client).await
    }

    // find sn in inner box
    pub async fn inner_find(&self, sn: &str, client: &Client) -> Result<MapSv, Value> {
        let cfg = self.cfg.get(client).await?;

        let boxv = self.cfg.inner_box(&cfg)?;

        self.find(&boxv, sn, client).await
    }

    async fn find(&self, boxv: &Vec<String>, sn: &str, client: &Client) -> Result<MapSv, Value> {
        let cmd = json!({ "line": self.line, "sn": sn });

        for box_sn in boxv.iter() {
            let path = self.get_path(box_sn);

            if let Ok(body) = client.cfgdb(&cmd, &format!("file/get/{path}")).await {
                let body = body.ck_obj("data")?;
                return Ok(body.clone());
            }
        }

        err!(ECode::Para, "no psn")
    }

    async fn cont(&self, boxv: &Vec<String>, client: &Client) -> Result<MapSv, Value> {
        let cmd = json!({ "line": self.line });
        let mut ret = MapSv::new();

        for box_sn in boxv.iter() {
            let path = self.get_path(box_sn);

            let body_box = match client.cfgdb(&cmd, &format!("file/list/{path}")).await {
                Ok(v) => v,
                Err(_) => {
                    continue;
                }
            };

            let body_box = body_box.ck_array("data")?;

            ret.insert(box_sn.to_string(), json!(body_box));
        }

        Ok(ret)
    }

    // set box's sn
    pub async fn set(&self, data: &MapSv, client: &Client) -> RNull {
        let box_sn = data.ck_str("box")?;
        let pos = data.ck_str("to")?;
        let sn = data.ck_str("sn")?;

        let p = self.get_path(box_sn);

        let cmd = json!({
            "line": self.line,
            "sn": sn,
            "v": {
                "sn": sn,
                "pos": pos,
            }
        });

        client.cfgdb(&cmd, &format!("file/md/{p}")).await?;

        Ok(())
    }

    // rm sn from the box
    pub async fn rm(&self, data: &MapSv, client: &Client) -> RNull {
        let box_sn = data.ck_str("box")?;
        let sn = data.ck_str("sn")?;

        let cmd = json!({ "line": self.line, "sn": sn });
        let p = self.get_path(box_sn);

        client.cfgdb(&cmd, &format!("file/rm/{p}")).await?;

        Ok(())
    }

    // find empty pos in outer box
    pub async fn empty_outer(&self, buf: &Buf, client: &Client) -> Result<(String, String), Value> {
        let cfg = self.cfg.get(client).await?;

        let boxv = self.cfg.outer_box(&cfg)?;
        let set = buf.get_set("outer", client).await;

        self.empty(&boxv, &set, client).await
    }

    // find empty pos in inner box
    pub async fn empty_inner(&self, buf: &Buf, client: &Client) -> Result<(String, String), Value> {
        let cfg = self.cfg.get(client).await?;

        let boxv = self.cfg.inner_box(&cfg)?;
        let set = buf.get_set("inner", client).await;

        self.empty(&boxv, &set, client).await
    }

    async fn empty(
        &self,
        boxv: &Vec<String>,
        buf: &HashSet<String>,
        client: &Client,
    ) -> Result<(String, String), Value> {
        let cmd = json!({ "line": self.line });

        for box_sn in boxv.iter() {
            let path = self.get_path(box_sn);

            let exist = client.cfgdb(&cmd, &format!("file/exist/{path}")).await?;
            let exist = exist.ck_bool("data")?;

            if !exist {
                return Ok((box_sn.to_string(), "1-1".to_string()));
            }

            let body_box = client.cfgdb(&cmd, &format!("file/list/{path}")).await?;
            let body_box = body_box.ck_array("data")?;

            let mut set = HashSet::new();
            let mut row = 0;
            let mut col = 0;

            for d in body_box.iter() {
                let d = d.ckobj()?;
                let sn = d.ck_str("sn")?;

                if sn == "attr" {
                    row = d.ck_i64("row")?;
                    col = d.ck_i64("col")?;
                    continue;
                }

                let pos = d.ck_str("pos")?;
                if buf.contains(pos) {
                    continue;
                }

                set.insert(pos.to_string());
            }

            if let Some(v) = self.find_empty_pos(&set, row, col) {
                return Ok((box_sn.to_string(), v.to_string()));
            }
        }

        err!(ECode::Para, "no empty pos")
    }

    fn find_empty_pos(&self, data: &HashSet<String>, row: i64, col: i64) -> Option<String> {
        for r in 1..=row {
            for c in 1..=col {
                let tmp = format!("{r}-{c}");
                if data.contains(&tmp) {
                    return Some(tmp);
                }
            }
        }

        None
    }

    fn get_path(&self, box_sn: &str) -> String {
        format!("selbox_{box_sn}")
    }
}
